package com.rockwell.maze800.views;

import java.text.DecimalFormat;
import java.util.List;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2;
import org.opencv.imgproc.Imgproc;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class MazeCameraView extends RelativeLayout implements OnTouchListener, CvCameraViewListener2, SensorEventListener {

	private static final String  TAG = "MazeCameraView::View";

    private boolean              mIsColorSelected = false;
    private Mat                  mRgba;
    private Scalar               mBlobColorRgba;
    private Scalar               mBlobColorHsv;
    private ColorBlobDetector    mDetector;
    private Mat                  mSpectrum;
    private Size                 SPECTRUM_SIZE;
    private Scalar               CONTOUR_COLOR;
    
    private CameraBridgeViewBase mOpenCvCameraView;
    private TextView 			 mCompassValueView;
    
    // Compass parameters
    private SensorManager 		 mSensorManager;
    private Sensor 				 mGsensor;
    private Sensor 				 mMsensor;
    private float[] 			 mGData;
    private float[] 			 mMData;
    private float[]				 mOrientation = new float[3];
    private int 				 mCount;
    
    private BaseLoaderCallback  mLoaderCallback = new BaseLoaderCallback(this.getContext()) {
        @Override
        public void onManagerConnected(int status) {
            switch (status) {
                case LoaderCallbackInterface.SUCCESS:
                {
                    Log.i(TAG, "OpenCV loaded successfully");
                    mOpenCvCameraView.enableView();
                    mOpenCvCameraView.setOnTouchListener(MazeCameraView.this);
                } break;
                default:
                {
                    super.onManagerConnected(status);
                } break;
            }
        }
    };

	public MazeCameraView(Context context) {
		super(context);
		init(context);
	}
	
	public MazeCameraView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context);
	}
	
	public MazeCameraView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init(context);
	}

	@Override
	protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
		super.onLayout(arg0, arg1, arg2, arg3, arg4);
	}

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
	}

	@Override
	public void onSensorChanged(SensorEvent event) {
		int type = event.sensor.getType();
		if (type == Sensor.TYPE_ACCELEROMETER)
			mGData = event.values;
		else if (type == Sensor.TYPE_MAGNETIC_FIELD)
			mMData = event.values;

		if (mGData != null && mMData != null) {
	        //if (mCount++ > 50) {
	        	mCount = 0;

	        	float[] R = new float[9];
	        	float[] I = new float[9];
				
	        	if (SensorManager.getRotationMatrix(R, I, mGData, mMData)) {
	        		
	        		float[] data = new float[3];
	        		SensorManager.getOrientation(R, data);
	        	
		        	data[0] = (float)Math.toDegrees(data[0]);
		        	data[1] = (float)Math.toDegrees(data[1]);
		        	data[2] = (float)Math.toDegrees(data[2]);
		        	System.arraycopy(data, 0, mOrientation, 0, 3);
		        	  
		        	DecimalFormat displayFormat = new DecimalFormat("#.#");
		        	
		        	String sValues = "Azimuth:\t" + String.valueOf(displayFormat.format(mOrientation[0])) +
		                             "\nPitch:\t" + String.valueOf(displayFormat.format(mOrientation[1])) +
		                             "\nRoll:\t" + String.valueOf(displayFormat.format(mOrientation[2]));
		
		            Log.d("Compass", sValues);
		            mCompassValueView.setText(sValues);
	        	}
	        //}
		}
	}

	@Override
	public void onCameraViewStarted(int width, int height) {
        mRgba = new Mat(height, width, CvType.CV_8UC4);
        mDetector = new ColorBlobDetector();
        mSpectrum = new Mat();
        mBlobColorRgba = new Scalar(255);
        mBlobColorHsv = new Scalar(255);
        SPECTRUM_SIZE = new Size(200, 64);
        CONTOUR_COLOR = new Scalar(255,0,0,255);
    }

	@Override
	public void onCameraViewStopped() {
		mRgba.release();
	}

	@Override
	public boolean onTouch(View v, MotionEvent event) {
        int cols = mRgba.cols();
        int rows = mRgba.rows();

        int xOffset = (mOpenCvCameraView.getWidth() - cols) / 2;
        int yOffset = (mOpenCvCameraView.getHeight() - rows) / 2;

        int x = (int)event.getX() - xOffset;
        int y = (int)event.getY() - yOffset;

        Log.i(TAG, "Touch image coordinates: (" + x + ", " + y + ")");

        if ((x < 0) || (y < 0) || (x > cols) || (y > rows)) return false;

        Rect touchedRect = new Rect();

        touchedRect.x = (x>4) ? x-4 : 0;
        touchedRect.y = (y>4) ? y-4 : 0;

        touchedRect.width = (x+4 < cols) ? x + 4 - touchedRect.x : cols - touchedRect.x;
        touchedRect.height = (y+4 < rows) ? y + 4 - touchedRect.y : rows - touchedRect.y;

        Mat touchedRegionRgba = mRgba.submat(touchedRect);

        Mat touchedRegionHsv = new Mat();
        Imgproc.cvtColor(touchedRegionRgba, touchedRegionHsv, Imgproc.COLOR_RGB2HSV_FULL);

        // Calculate average color of touched region
        mBlobColorHsv = Core.sumElems(touchedRegionHsv);
        int pointCount = touchedRect.width*touchedRect.height;
        for (int i = 0; i < mBlobColorHsv.val.length; i++)
            mBlobColorHsv.val[i] /= pointCount;

        mBlobColorRgba = converScalarHsv2Rgba(mBlobColorHsv);

        Log.i(TAG, "Touched rgba color: (" + mBlobColorRgba.val[0] + ", " + mBlobColorRgba.val[1] +
                ", " + mBlobColorRgba.val[2] + ", " + mBlobColorRgba.val[3] + ")");

        mDetector.setHsvColor(mBlobColorHsv);

        Imgproc.resize(mDetector.getSpectrum(), mSpectrum, SPECTRUM_SIZE);

        mIsColorSelected = true;

        touchedRegionRgba.release();
        touchedRegionHsv.release();

        return false; // don't need subsequent touch events
    }
	
	public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
        mRgba = inputFrame.rgba();

        if (mIsColorSelected) {
            mDetector.process(mRgba);
            List<MatOfPoint> contours = mDetector.getContours();
            Log.e(TAG, "Contours count: " + contours.size());
            Imgproc.drawContours(mRgba, contours, -1, CONTOUR_COLOR);
            mDetector.drawContourCenters(mRgba);

            Mat colorLabel = mRgba.submat(4, 68, 4, 68);
            colorLabel.setTo(mBlobColorRgba);

            Mat spectrumLabel = mRgba.submat(4, 4 + mSpectrum.rows(), 70, 70 + mSpectrum.cols());
            mSpectrum.copyTo(spectrumLabel);
        }

        return mRgba;
    }

    private Scalar converScalarHsv2Rgba(Scalar hsvColor) {
        Mat pointMatRgba = new Mat();
        Mat pointMatHsv = new Mat(1, 1, CvType.CV_8UC3, hsvColor);
        Imgproc.cvtColor(pointMatHsv, pointMatRgba, Imgproc.COLOR_HSV2RGB_FULL, 4);

        return new Scalar(pointMatRgba.get(0, 0));
    }
	
	private void init(Context context) {
		Log.i(TAG, "called init");
		
		LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		inflater.inflate(R.layout.maze_camera_layout, this);
		
        mCompassValueView = (TextView) findViewById(R.id.compass_values);
        mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.opencv_view);
        mOpenCvCameraView.setCvCameraViewListener(this);
        
        mSensorManager = (SensorManager)getContext().getSystemService(Context.SENSOR_SERVICE);
        mGsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mMsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
	}
	
	public void onActivated()
    {
        OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_7, getContext(), mLoaderCallback);

        mSensorManager.registerListener(this, mGsensor, SensorManager.SENSOR_DELAY_GAME);
        mSensorManager.registerListener(this, mMsensor, SensorManager.SENSOR_DELAY_GAME);
    }
	
	public void onDeactivated()
    {
        if (mOpenCvCameraView != null)
            mOpenCvCameraView.disableView();
        
        mSensorManager.unregisterListener(this);
    }
}
